#include "DelegateType.h"

#include "PlaceholderType.h"

#include "GenericInfo.h"

#include "Containers/ScriptDelegate.h"
#include "Containers/ScriptDelegateImplement.h"

#include "NativeFunction.h"
#include "FunctionList.h"

#include "FunctionType.h"
#include "TypeInstance.h"


CScriptDelegateType::CScriptDelegateType()
    :CType(
        STypeID(CType::GetEngineCoreNamespace()+U"::TScriptDelegate",true)
        ,ETypeFlags::Generic | ETypeFlags::ScriptType | ETypeFlags::Delegate
       )
{
    GenericInfo=new CGenericInfo();

    auto ReturnTypePlaceholder= new CPlaceholderType(
        STypeID(GetFullName()+U"::ReturnType",true)
        ,ETypeFlags::None
        ,0
        ,this);

    auto ParametersTypePlaceholder= new CPlaceholderType(
        STypeID(GetFullName()+U"::Args...",true)
        ,ETypeFlags::None
        ,0
        ,this);

    ParametersTypePlaceholder->SetIsList(true);

    GenericInfo->PlaceholderTypes.Add(ReturnTypePlaceholder);
    GenericInfo->PlaceholderTypes.Add(ParametersTypePlaceholder);

    // auto FunctionTypePlaceholder= new CPlaceholderType(
    //     STypeID(GetFullName()+U"::Placeholder_FunctionType",true)
    //     ,U"FunctionType"
    //     ,ETypeFlags::None
    //     ,0
    //     ,this);

    //add functions
    Functions= new CFunctionList(this);

    SDecoratedType ObjectType;
    ObjectType.BasicType= WH::TReflectionType<TSharedObjectPtr<RObject>>::FindOrCreateType();

    SDecoratedType FunctionType;
    FunctionType.BasicType= CFunctionType::GetStaticType();
    FunctionType.OverrideGenericTypes.Add(ReturnTypePlaceholder);
    FunctionType.OverrideGenericTypes.Add(ParametersTypePlaceholder);

    SDecoratedType ThisType;
    ThisType.BasicType=this;
    ThisType.OverrideGenericTypes.Add(ReturnTypePlaceholder);
    ThisType.OverrideGenericTypes.Add(ParametersTypePlaceholder);

    {
        //add a constructor
        //two parameter, a object ptr and a function ptr
        auto ConstructorFunction = new CNativeFunction (
            this->Functions
            , CFunction::GetConstructorName()
            , CGetType<void>()()
            ,{
                SDecoratedType(WH::TReflectionType<TSharedObjectPtr<RObject>>::FindOrCreateType())
                ,FunctionType
            }
            ,{}
            );
    }

    {
        //add Invoke function
        auto InvokeFunction = new CNativeFunction(
            this->Functions
            , U"Invoke"
            , ReturnTypePlaceholder
            , {ParametersTypePlaceholder}
            ,{}
            );
    }

    {
        //add Add function
        auto AddFunction = new CNativeFunction (
            this->Functions
            , U"AddFunction"
            , ThisType
            ,{
                ThisType 
                ,ObjectType
                ,FunctionType
            }
            ,
            {}
            ); 

        AddFunction->bIsStatic=true;
    }

    {
        //add Remove function
        auto RemoveFunction = new CNativeFunction (
            this->Functions
            , U"RemoveFunction"
            , ThisType
            ,{
                ThisType
                ,ObjectType
                ,FunctionType
            }
            ,{}
            ); 

        RemoveFunction->bIsStatic=true;
    }

    {
        //add static Append two delegate
        auto AppendFunction = new CNativeFunction (
            this->Functions
            , U"Append"
            , ThisType
            ,{
                ThisType,ThisType
            }
            ,{}
            );
        AppendFunction->bIsStatic=true;
    }

    {
        //add static Remove function
        auto RemoveFunction = new CNativeFunction (
            this->Functions
            , U"Remove"
            , ThisType
            ,{
                ThisType,ThisType
            }
            ,{}
            );

        RemoveFunction->bIsStatic=true;
    }

    {
        //add Clear function
        auto ClearFunction = new CNativeFunction (
            this->Functions
            , U"Clear"
            , CGetType<void>()()
            ,{}
            ,{}
            );
    }
}

CScriptDelegateType* CScriptDelegateType::GetStaticType()
{
    static CScriptDelegateType StaticType;
    return &StaticType;
}


CString CScriptDelegateType::CreateTypeName(const CString &ReturnType, const TVector<CString> &ParamTypes)
{
    TVector <CString> AllArugments;
    AllArugments.Add(ReturnType);
    for(auto ParamType : ParamTypes)
    {
        AllArugments.Add(ParamType);
    }

    TVector <CString> AllArugmentPlaceholders;
    AllArugmentPlaceholders.Add(U"ReturnType");
    for(int32_t i=0;i<ParamTypes.Num();i++)
    {
        AllArugmentPlaceholders.Add(U"Arg"+CString(i));
    }
    
    return STypeID::CreateTemplateTypeName(
        GetStaticType()->GetFullName()
        ,AllArugments
        ,AllArugmentPlaceholders
        );
}

STypeID CScriptDelegateType::CreateID(const SDecoratedType &ReturnType, const TVector<SDecoratedType> &ParamTypes)
{
    TVector<STypeID> AllParameters;
    for(auto ParamType : ParamTypes)
    {
        AllParameters.Add(ParamType.BasicType->ID);
    }
   
    return CreateID(ReturnType.BasicType->ID,AllParameters);
}

STypeID CScriptDelegateType::CreateID(const STypeID &ReturnType, const TVector<STypeID> &ParamTypes)
{
    TVector<STypeID> AllArguments;
    AllArguments.Add(ReturnType);
    AllArguments.Append(ParamTypes);
    
    TVector <CString> AllArugmentPlaceholders;
    AllArugmentPlaceholders.Add(U"ReturnType");
    for(int32_t i=0;i<ParamTypes.Num();i++)
    {
        AllArugmentPlaceholders.Add(U"Arg"+CString(i));
    }

    return STypeID(
        GetStaticType()->ID
        ,AllArguments
        ,AllArugmentPlaceholders
    );
}

static uint8_t RegisterScriptDelegateTypes= (
    CScriptDelegateType::GetStaticType()
    ,0
);

class CDelegateInstance : public ITypeInstance
{
protected:
    CScriptDelegateGenerialized* Type;
public:
    CDelegateInstance(CScriptDelegateGenerialized* InType)
        :Type(InType)
    {
    }

    void InitInstance(void* Memory) override
    {
        new(Memory) SScriptDelegateBase(Type->GetFunctionType());
    }

    void CloneInstance(const void* Instance, void* Memory) override
    {
        new(Memory) SScriptDelegateBase(*(SScriptDelegateBase*)Instance);
    }

    void EraseInstance(void* InValue) override
    {
        ((SScriptDelegateBase*)InValue)->~SScriptDelegateBase();
    }
    
    void* CreateInstance() override
    {
        return new SScriptDelegateBase(Type->GetFunctionType());
    }

    void ResetAllocated(void* InMemory) override
    {
        reinterpret_cast<SScriptDelegateBase*>(InMemory)->Clear();
    }

    uint32_t GetSize() override
    {
        return sizeof(SScriptDelegateBase);
    }

    CString ToDebugString(const void* InInstance) const override
    {
        return Type->GetFullName();
    }

    uint64_t GetInstanceHash(const void* InInstance) const override
    {
        return ((const SScriptDelegateBase *)InInstance)->GetHashCode();
    }

    bool IsInstanceEqual(const void* A, const void* B) const override
    {
        return ((const SScriptDelegateBase *)A)
            ->IsEqual(*(const SScriptDelegateBase *)B);
    }

};


CScriptDelegateGenerialized::CScriptDelegateGenerialized(
    CFunctionTypeGeneralized* InFunctionType
    ,ETypeFlags InExtraFlags
    )
    :CType(
        CScriptDelegateType::CreateID(InFunctionType->GetReturnType(),InFunctionType->GetParameterTypes())
        ,   InExtraFlags
            | ETypeFlags::Generalized 
            | ETypeFlags::Delegate 
            |(InFunctionType->IsScriptType()?ETypeFlags::Script : ETypeFlags::None)
            |(InFunctionType->CanUseInScript()? ETypeFlags::None : ETypeFlags::CantUseInScript)
       )
{
    SetTypeArguments(InFunctionType);

    Instancable=new CDelegateInstance(this);
}


void CScriptDelegateGenerialized::SetTypeArguments(CFunctionTypeGeneralized *InFunctionType)
{
    FunctionType=InFunctionType;
    GeneralizedInfo = new CGeneralizedInfo();
    GeneralizedInfo->TemplateType= CScriptDelegateType::GetStaticType();
    GeneralizedInfo->TypeArguments.Add(InFunctionType->GetReturnType().BasicType);
    for(auto& DecoratedParamType : InFunctionType->GetParameterTypes() )
    {
        assert(!DecoratedParamType.BasicType->IsGenericType());
        GeneralizedInfo->TypeArguments.Add(DecoratedParamType.BasicType);
    }

    //add to generic info
    reinterpret_cast<CGenericInfo*>(CScriptDelegateType::GetStaticType()->GenericInfo)->GeneralizedTypes.Add(this);
    reinterpret_cast<CGenericInfo*>(CScriptDelegateType::GetStaticType()->GenericInfo)
        ->GenericMap.Add(GeneralizedInfo->TypeArguments ,this);

    //add functions 
    Functions= new CFunctionList(this);
    {
        //add a constructor
        //two parameter, a object ptr and a function ptr
        auto ConstructorFunction = new CNativeFunction (
            this->Functions
            , CFunction::GetConstructorName()
            , CGetType<void>()()
            ,{
                WH::TReflectionType<TSharedObjectPtr<RObject>>::FindOrCreateType()
                ,InFunctionType
            }
            ,{}
            );

        ConstructorFunction->SetCppBody([](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* Delegate = *reinterpret_cast<SScriptDelegateBase**>(InArgs[0].GetPointer());
            auto Object=AnyCast<RObjectPtr>(InArgs[1]);

            assert(InArgs[2].GetType()->IsFunctionType());
            CFunction** Function= reinterpret_cast<CFunction**>(InArgs[2].GetPointer());
            Delegate->Bind(Object,*Function);
            return CAny();
        });
    }

    {
        //add Invoke function
        auto InvokeFunction = new CNativeFunction(
            this->Functions
            , U"Invoke"
            , InFunctionType->GetReturnType()
            ,InFunctionType->GetParameterTypes()
            ,{}
            );

        InvokeFunction->SetCppBody([](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* Delegate = *reinterpret_cast<SScriptDelegateBase**>(InArgs[0].GetPointer());
            TVector<CAny> Args= InArgs.Slice(1,InArgs.Num());
            return Delegate->ScriptInvoke({Args});
        });

    }

    {
        //add Add function
        auto AddFunction = new CNativeFunction (
            this->Functions
            , U"AddFunction"
            , this
            ,{
                this
                ,WH::TReflectionType<TSharedObjectPtr<RObject>>::FindOrCreateType()
                ,InFunctionType
            }
            ,
            {}
            ); 

        AddFunction->bIsStatic=true;

        AddFunction->SetCppBody([this](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* Delegate = reinterpret_cast<SScriptDelegateBase*>(InArgs[0].GetPointer());
            auto Object=AnyCast<RObjectPtr>(InArgs[1]);

            assert(InArgs[2].GetType()->IsFunctionType());
            CFunction** Function= reinterpret_cast<CFunction**>(InArgs[2].GetPointer());
            return CAny(this, SScriptDelegateBase::AddFunction(Delegate,Object,*Function));
        });

    }

    {
        //add Remove function
        auto RemoveFunction = new CNativeFunction (
            this->Functions
            , U"RemoveFunction"
            , this
            ,{
                this
                ,WH::TReflectionType<TSharedObjectPtr<RObject>>::FindOrCreateType()
                ,InFunctionType
            }
            ,{}
            ); 

        RemoveFunction->bIsStatic=true;

        RemoveFunction->SetCppBody([this](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* Delegate = reinterpret_cast<SScriptDelegateBase*>(InArgs[0].GetPointer());
            auto Object=AnyCast<RObjectPtr>(InArgs[1]);

            assert(InArgs[2].GetType()->IsFunctionType());
            CFunction** Function= reinterpret_cast<CFunction**>(InArgs[2].GetPointer());
            return CAny(this, SScriptDelegateBase::RemoveFunction(Delegate,Object,*Function));
        });

    }

    {
        //add static Append two delegate
        auto AppendFunction = new CNativeFunction (
            this->Functions
            , U"Append"
            , this
            ,{
                this,this
            }
            ,{}
            );
        AppendFunction->bIsStatic=true;

        AppendFunction->SetCppBody([this](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* A =reinterpret_cast<SScriptDelegateBase*>(InArgs[0].GetPointer());
            SScriptDelegateBase* B =reinterpret_cast<SScriptDelegateBase*>(InArgs[1].GetPointer());
            return CAny(this, SScriptDelegateBase::Append(A,B));
        });
    }

    {
        //add static Remove function
        auto RemoveFunction = new CNativeFunction (
            this->Functions
            , U"Remove"
            , this
            ,{
                this,this
            }
            ,{}
            );

        RemoveFunction->bIsStatic=true;

        RemoveFunction->SetCppBody([this](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* A =reinterpret_cast<SScriptDelegateBase*>(InArgs[0].GetPointer());
            SScriptDelegateBase* B =reinterpret_cast<SScriptDelegateBase*>(InArgs[1].GetPointer());
            return CAny(this, SScriptDelegateBase::Remove(A,B));
        });

    }

    {
        //add Clear function
        auto ClearFunction = new CNativeFunction (
            this->Functions
            , U"Clear"
            , CGetType<void>()()
            ,{}
            ,{}
            );

        ClearFunction->SetCppBody([](const TVector<CAny> & InArgs)->CAny
        {
            SScriptDelegateBase* Delegate = *reinterpret_cast<SScriptDelegateBase**>(InArgs[0].GetPointer());
            Delegate->Clear();
            return CAny();
        });
    }

    
}

SDecoratedType CScriptDelegateGenerialized::GetReturnType() const
{
    return GetFunctionType()->GetReturnType();
}

TVector<SDecoratedType> CScriptDelegateGenerialized::GetParameterTypes() const
{
    return GetFunctionType() ->GetParameterTypes();
}

CFunctionTypeGeneralized *CScriptDelegateGenerialized::GetFunctionType() const
{
    return FunctionType;
}

int64_t CScriptDelegateGenerialized::GetBindingNum(const void *InInstance) const
{
    return 0;
}

